feat: structured constraints for instruments and beyond#424
Conversation
jamesandersen
left a comment
There was a problem hiding this comment.
@raginpirate thanks for putting #424 together — I do think this proposal works well:
- (+) Introduces a new credential type
network_token - (+) Required fields are explicit via the new
constraintprimitive- e.g. under instrument -> credentials -> constraints -> required_fields (more like where #288 started but now implemented at the right level)
- In contrast to where #288 was trending with implicit constraints via instrument -> constraints -> requires_card_verification and docs to explain intent at the credential level
- (+) Cross-domain reuse e.g.
address_constraint.json,credential_constraint.json - (-) Breaking change with removal of
card_number_type... left separate comment on this
On the "credential inception" point e.g. distinct constraint on a wire credential and - where applicable - the funding source underlying it. What do you think about taking this on in a separate PR? I'd like to better understand if there is a concrete scenario requiring both? e.g. can a business require a CVV on the underlying instrument while only accepting the network token? It's been a winding road so far to get alignment on updated constraint modeling for just the "wire credential" ... hopefully a follow-on for the underlying funding source could be quicker and cleaner after landing this.
TBH ... it took a while to grok this all (in large part for lack of good focus time) but I think the resolved examples are actually much easier than the schema suggests at first glance - just as another sanity check the multiple entries under credentials is how we address the hurdle that led us to the implicit bool and AVS enum on #288. Look right?
{
"available_instruments": [
{
"type": "card", // card-based instrument
"constraints": {
"brands": ["visa", "mastercard"],
"required_fields": ["billing_address"],
"billing_address": {
"required_fields": ["postal_code"] // e.g. AVS1, but adapts to other schemes
},
"credentials": [
{
"type": "card", // FPAN
"constraints": { "required_fields": ["cvc"] }
},
{
"type": "network_token", // DPAN / CPAN
"constraints": { "required_fields": ["cryptogram"] } // could add ECI if a business needs it
}
]
}
},
]
}| "const": "card", | ||
| "description": "The credential type identifier for card credentials." | ||
| }, | ||
| "card_number_type": { |
There was a problem hiding this comment.
Should we deprecate rather than removing to avoid a breaking change?
| }, | ||
| "cryptogram": { | ||
| "type": "string", | ||
| "description": "Per-transaction cryptogram. Structurally required — a network token credential without a cryptogram is a CVC-verified card; submit it as `card_credential` instead." |
There was a problem hiding this comment.
nit: Could we indicate this may be either long form or short form (e.g. dCVV)
| { | ||
| "type": "object", | ||
| "required": ["type", "number", "cryptogram", "eci_value"], | ||
| "properties": { |
There was a problem hiding this comment.
Thoughts about adding token_requestor_id (#296) onto network_token_credential while we're at it? not widely used as discussed on that PR but we do have the Braintree BYOT example
| @@ -0,0 +1,61 @@ | |||
| { | |||
| "$schema": "https://json-schema.org/draft/2020-12/schema", | |||
| "$id": "https://ucp.dev/schemas/shopping/types/network_token_credential.json", | |||
|
Schema read only. One validation gap to keep an eye on if this moves from prototype to spec shape: "constraints": {
"$ref": "constraint.json"
}Because of that, JSON Schema validation will not use { "type": "card", "constraints": { "required_fields": ["cryptogram"] } }
{ "type": "network_token", "constraints": { "required_fields": ["cvc"] } }Same issue for Suggested direction: either make The interoperability risk is that merchants/platforms may think constraint payloads are statically validated, while cross-axis mistakes only exist in prose. That matters for checkout because a mistyped funding-source requirement can become a failed tokenization/payment path rather than a schema error. |
Prototype revision related to #288
This is heavily AI generated to quickly show the thinking and primitives; do not trust the descriptions or small implementation quirks here!
What
Introduces a single constraint primitive (constraint.json) and applies it consistently across instrument and credential availability. Replaces ad-hoc booleans (requires_card_verification,
billing_address_granularity) with a uniform shape: required_fields[] plus domain-specific custom keys.
New primitives
annotation.
Reshaped
behind the wire credential. Constraints sit on the semantically correct axis only — never duplicated.
Cross-domain reuse
fulfillment_available_method.json adopts the same primitives — top-level constraints extending constraint.json, with destination reusing address_constraint.json. Closes the address-constraint gap on the
fulfillment side without inventing parallel concepts.
Example
Reads as: handler accepts a token credential on the wire, conveying either a CVC-verified card or a cryptogram-verified network token. Merchant requires CVC on raw cards, requires a billing address with at least
postal code + country, and accepts visa/mastercard.